#!/bin/bash
##########################################################
#   update_check_rpmbin.sh:
#
#     -- 检测RPM和二进制的变更
#
# 说明:
#          工具: update_check_rpmbin.sh
#
#          功能: 检测软件升级前后的RPM包和二进制的差异
#
#          用法:
#              --$1: 升级前rpm所在目录
#              --$2: 升级后rpm所在目录
#
#
# 变更日志:
# 2021/1/19 muc  -- change目录下文件增加txt后缀，方便在Windows中打开
#
# 2021/1/7  muc  -- 修复tree output目录失败的问题
#
# 2020/9/16 muc  -- 优化工具脚本：
#                       1. 增加执行过程log打印
#                       2. 规范变量命名及使用规范
#                       3. 增加参数校验
#                       4. 提升生成清单及变更的易读性
#                       5. 增加二进制文件大小变更值并设置阈值筛选
#
# 2020/9/11 muc  -- 将说明，注释的英文改为中文
#
# 2020/7/11 muc  -- 添加update_check_rpmbin.sh，对比软件升级前后的差异
##########################################################

# 工作路径
WORK_PATH=$PWD
# 临时目录
TMP_PATH=$WORK_PATH/tmp_$(date +%Y%m%d%H%M%S)
# 输出产物
OUTPUT=$WORK_PATH/output

# 大小变化筛选阈值
THRESHOLD="0"

# 语言设置为英文
LANG=en_US.UTF-8

# 入参
PARAMETER1=$1
PARAMETER2=$2

################
# 信息打印
################
function log_output()
{
    if [ "$#" -ne 2 ]; then
        log_output 3 "log日志函数存在入参有误"
    fi

    # 输出润色
    case $1 in
        0)
            # 绿色 正确
            echo -e "[ $(date "+%F %H:%M:%S") ]    [ \033[32m$2\033[0m ]"
            ;;
        1)
            # 红色 错误
            echo -e "[ $(date "+%F %H:%M:%S") ]    [ \033[31m$2\033[0m ]"
            ;;
        2)
            # 蓝色 提示
            echo -e "[ $(date "+%F %H:%M:%S") ]    [ \033[36m$2\033[0m ]"
            ;;
        3)
            # 黄色 警告
            echo -e "[ $(date "+%F %H:%M:%S") ]    [ \033[33m$2\033[0m ]"
            ;;
    esac
}

##################
# 入参校验
##################
function parameter_check()
{
    log_output 2 "开始进行入参校验"
    # 参数个数校验
    if [ "$#" -ne 2 ]; then
        log_output 1 "入参个数错误"
        echo "例如: sh $0 A B"
        return 1
    else
        # 参数类型校验
        if [ ! -d "$1" ] || [ ! -d "$2" ]; then
            log_output 1 "入参类型存在错误"
            echo "参数为包含rpm的升级目录"
            return 1
        fi
        log_output 0 "入参校验已完成，参数个数及类型正确"
    fi
}

##################
# 初始化工作目录
##################
function init_dir_file()
{
    log_output 2 "初始化工作目录"
    # 初始化输出目录
    rm -rf $OUTPUT
    mkdir -p $OUTPUT/BIN/list
    mkdir -p $OUTPUT/BIN/change
    mkdir -p $OUTPUT/RPM/list
    mkdir -p $OUTPUT/RPM/change

    # 创建临时目录
    mkdir -p $TMP_PATH
}

########################
# 准备升级目录rpm
########################
function dir_cp_check()
{
    log_output 2 "开始拷贝${2}到工作目录"
    # 工作目录
    cd $WORK_PATH

    # 建立目录
    mkdir -p $TMP_PATH/update_$2/xxx_rpm
    cp $1/*rpm $TMP_PATH/update_$2/xxx_rpm >/dev/null 2>&1

    # 列出rpm列表
    ls $TMP_PATH/update_$2/xxx_rpm > $TMP_PATH/update_$2/xxx_list

    if [ ! -s $TMP_PATH/update_$2/xxx_list ]; then
        log_output 1 "${2}目录下无rpm包"
        clean_end_show 1
        return 1
    else
        log_output 2 "${2}目录下rpm包拷贝成功"
    fi

    IFS_old=$IFS
    IFS=$'\n'

    # 二进制文件
    update_bin_check update_$2 $2

    # RPM包
    update_rpm_check update_$2 $2

    IFS=$IFS_old
}

#########################
# 二进制文件清单收集
#########################
function update_bin_check()
{
    local software_version
    local software_name
    local rpm_name
    local file_flag
    local file_permission
    local file_size
    local file_size_strip

    log_output 2 "开始收集${2}二进制文件清单"

    # 软件信息
    software_version=$(rpm -qpi $TMP_PATH/$1/xxx_rpm/$(head -n 1 $TMP_PATH/$1/xxx_list) | grep "^Version" | cut -d ":" -f 2 | sed "s/ //g")
    software_name=$(rpm -qpi $TMP_PATH/$1/xxx_rpm/$(head -n 1 $TMP_PATH/$1/xxx_list) | grep "^Source RPM" | cut -d ":" -f 2 | sed -r "s/-[^-]+-[^-]+$//" | sed "s/ //g")
    echo -e "[${software_name}:${software_version}]\n\n\n" > $OUTPUT/BIN/list/${2}_bin_base
    echo -e "[${software_name}:${software_version}]\n\n\n" > $OUTPUT/BIN/list/${2}_bin_rely

    for rpmer in $(cat $TMP_PATH/$1/xxx_list)
    do
        mkdir -p $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio

        rpm_name=$(rpm -qpi $TMP_PATH/$1/xxx_rpm/$rpmer | head -n 1 | cut -d ":" -f 2)

        cd $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio
        rpm2cpio $TMP_PATH/$1/xxx_rpm/$rpmer | cpio -id --quiet

        for filer in $(rpm -qpl $TMP_PATH/$1/xxx_rpm/$rpmer)
        do
            if [ $filer == "(contains no files)" ]; then
                log_output 3 "存在空包：$rpmer"
                continue
            fi

            file_flag=$(ls -ld $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} | cut -b 1)

            file_permission=$(ls -ld $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} | cut -b 1-10)

            if [ "$file_flag" = "-" ];then
                file_size=$(ls -ld $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} | awk '{print $5}')
                if [ $? -eq 0 ];then
                    strip $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} 2>/dev/null
                    if [ $? -eq 0 ];then
                        file_size_strip=$(ls -ld $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} | awk '{print $5}')
                    else
                        file_size_strip=""
                    fi
                fi
            else
                file_size=""
                file_size_strip=""
            fi

            echo "${rpm_name}:'${filer}' \ ${file_permission} \ ${file_size} ${file_size_strip}" >> $OUTPUT/BIN/list/${2}_bin_base

            file $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} | grep ELF >/dev/null 2>&1
            if [ $? -eq 0 ];then
                readelf -d $TMP_PATH/$1/xxx_rpm/${rpmer}_cpio${filer} | grep NEEDED | awk '{print $5}' > $TMP_PATH/$1/xxx_rpm/${2}_rely_list
                sort $TMP_PATH/$1/xxx_rpm/${2}_rely_list -o $TMP_PATH/$1/xxx_rpm/${2}_rely_list
                for relyer in $(cat $TMP_PATH/$1/xxx_rpm/${2}_rely_list)
                do
                    echo "${rpm_name}:'${filer}' $relyer" >> $OUTPUT/BIN/list/${2}_bin_rely
                done
            fi
        done
    done

    # 处理行首的空格
    sed -i "s/^ //" $OUTPUT/BIN/list/${2}_bin_base
    sed -i "s/^ //" $OUTPUT/BIN/list/${2}_bin_rely

    log_output 2 "${2}二进制文件清单收集已完成"
}

#####################
# rpm包清单收集
#####################
function update_rpm_check()
{
    declare -i rpm_size

    local rpm_name
    local rpm_size_12

    log_output 2 "开始收集${2}rpm包清单"

    for rpmer in $(cat $TMP_PATH/$1/xxx_list)
    do
        rpm_name=$(rpm -qpi $TMP_PATH/$1/xxx_rpm/$rpmer | grep "^Name" | cut -d ":" -f 2)
        echo "${rpm_name}" >> $OUTPUT/RPM/list/${2}_rpm_name

        rpm_size=$(ls -l $TMP_PATH/$1/xxx_rpm/$rpmer | awk '{print $5}')
        if [ "${rpm_size}" -gt 1024 ]; then
            if [ "${rpm_size}" -gt 1048576 ]; then
                if [ "${rpm_size}" -gt 1073741824 ]; then
                    rpm_size_12=$(echo "scale=2;(${rpm_size}/1073741824)" | bc)
                    echo "${rpm_name}: ${rpm_size_12}G" >> $OUTPUT/RPM/list/${2}_rpm_size
                else
                    rpm_size_12=$(echo "scale=2;(${rpm_size}/1048576)" | bc)
                    echo "${rpm_name}: ${rpm_size_12}M" >> $OUTPUT/RPM/list/${2}_rpm_size
                fi
            else
                rpm_size_12=$(echo "scale=2;(${rpm_size}/1024)" | bc)
                echo "${rpm_name}: ${rpm_size_12}K" >> $OUTPUT/RPM/list/${2}_rpm_size
            fi
        else
            echo "${rpm_name}: ${rpm_size}" >> $OUTPUT/RPM/list/${2}_rpm_size
        fi
    done

    # 处理行首的空格
    sed -i "s/^ //" $OUTPUT/RPM/list/${2}_rpm_name
    sed -i "s/^ //" $OUTPUT/RPM/list/${2}_rpm_size

    log_output 2 "${2}rpm包清单收集已完成"
}

###############################################
# 单独整理二进制文件的变更，提升结果易读性
###############################################
function handle_bin_file_change()
{
    awk '{print $1}' $OUTPUT/BIN/list/P1_bin_base > $OUTPUT/BIN/list/P1_bin_file
    awk '{print $1}' $OUTPUT/BIN/list/P2_bin_base > $OUTPUT/BIN/list/P2_bin_file

    diff -Nurp $OUTPUT/BIN/list/P1_bin_file $OUTPUT/BIN/list/P2_bin_file > $OUTPUT/BIN/change/bin_file.txt
}

####################
# 清单差异对比分析
####################
function diff_change()
{
    log_output 2 "开始对比二进制文件差异"
    # BIN 生成对比数据
    diff -Nurp $OUTPUT/BIN/list/P1_bin_base $OUTPUT/BIN/list/P2_bin_base > $OUTPUT/BIN/change/bin_base.txt
    diff -Nurp $OUTPUT/BIN/list/P1_bin_rely $OUTPUT/BIN/list/P2_bin_rely > $OUTPUT/BIN/change/bin_rely.txt
    # 生成文件的差异，不包含权限和大小
    handle_bin_file_change
    # 生成文件的大小变化
    bin_size_compare

    log_output 2 "二进制文件差异对比已完成"

    log_output 2 "开始对比rpm包差异"
    # RPM 生成对比数据
    diff -Nurp $OUTPUT/RPM/list/P1_rpm_name $OUTPUT/RPM/list/P2_rpm_name > $OUTPUT/RPM/change/rpm_name.txt
    if [ ! -s $OUTPUT/RPM/change/rpm_name.txt ]; then
        echo "No rpm changed." >> $OUTPUT/RPM/change/rpm_name.txt
    fi

    diff -Nurp $OUTPUT/RPM/list/P1_rpm_size $OUTPUT/RPM/list/P2_rpm_size > $OUTPUT/RPM/change/rpm_size.txt
    log_output 2 "rpm包差异对比已完成"
}

########################
# 二进制文件大小变更
########################
function bin_size_compare()
{
    declare -i p1_size
    declare -i p2_size
    declare -i p1_size_strip
    declare -i p2_size_strip
    declare -i size_change
    declare -i size_strip_change

    # 仅考虑普通文件的大小变化
    sed "/\\\ [ld]/d" $OUTPUT/BIN/list/P1_bin_base | awk '{print $1" "$5" "$6}' | sed '1,4d' > $TMP_PATH/P1_file_size
    sed "/\\\ [ld]/d" $OUTPUT/BIN/list/P2_bin_base | awk '{print $1" "$5" "$6}' | sed '1,4d' > $TMP_PATH/P2_file_size

    diff $TMP_PATH/P1_file_size $TMP_PATH/P2_file_size > $TMP_PATH/bin_size_ret

    # 如果文件大小存在变化则计算变化大小
    if [ -s $TMP_PATH/bin_size_ret ]; then
        grep "<" $TMP_PATH/bin_size_ret | sed "s/^< //" > $TMP_PATH/P_1_size
        grep ">" $TMP_PATH/bin_size_ret | sed "s/^> //" > $TMP_PATH/P_2_size

        sort $TMP_PATH/P_1_size | awk '{print $1}' > $TMP_PATH/P_1_file
        sort $TMP_PATH/P_2_size | awk '{print $1}' > $TMP_PATH/P_2_file

        # 交集得出变化的大小
        comm -12 $TMP_PATH/P_1_file $TMP_PATH/P_2_file > $TMP_PATH/P_name

        for size in $(cat $TMP_PATH/P_name)
        do
            # 原始大小
            p1_size=$(grep "${size}" $TMP_PATH/P_1_size | awk '{print $2}')
            p2_size=$(grep "${size}" $TMP_PATH/P_2_size | awk '{print $2}')

            # strip后大小
            p1_size_strip=$(grep "${size}" $TMP_PATH/P_1_size | awk '{print $3}')
            p2_size_strip=$(grep "${size}" $TMP_PATH/P_2_size | awk '{print $3}')

            # 整理输出
            if [ "$p1_size" -gt "$p2_size" ];then
                size_change=$(($p1_size - $p2_size))
                size_strip_change=$(($p1_size_strip - $p2_size_strip))
                size_count $size $size_change $size_strip_change -
            else
                size_change=$(($p2_size - $p1_size))
                size_strip_change=$(($p2_size_strip - $p1_size_strip))
                size_count $size $size_change $size_strip_change +
            fi
        done
    fi
}

######################
# 计算大小变化值
######################
function size_count()
{
    local strip_size_str

    # 处理strip后的大小
    if [ "$3" -eq "0" ]; then
        strip_size_str=""
    else
        strip_size_str="($3)"
    fi

    # 结果输出到文件
    if [ "$2" -gt "$THRESHOLD" ]; then
        if [ "$2" -gt 1024 ]; then
            echo "$1  -------  $4$(echo "scale=3;($2/1024)" | bc)K$strip_size_str" >> $OUTPUT/BIN/change/bin_size.txt
        else
            echo "$1  -------  $4$2$strip_size_str" >> $OUTPUT/BIN/change/bin_size.txt
        fi
    fi
}

#######################
# 清理中间文件
#######################
function clean_end_show()
{
    log_output 2 "中间文件已清理"
    rm -rf $TMP_PATH

    if [ "$1" == "0" ]; then
        sh_end_show
    fi
}

###########################
# 执行成功，结束
###########################
function sh_end_show()
{
    log_output 0 "脚本执行成功，结果保存于当前目录下output目录"
    echo " output 目录结构："
    tree $OUTPUT --charset ASCII
}

############
# 主函数
############
function main()
{
    # 入参检测
    parameter_check $PARAMETER1 $PARAMETER2 || exit 1

    # 初始化
    init_dir_file

    # 拷贝rpm文件并检测生成清单
    dir_cp_check $PARAMETER1 P1 || exit 1
    dir_cp_check $PARAMETER2 P2 || exit 1

    # 对比清单，呈现差异
    diff_change

    # 清理中间文件
    clean_end_show 0
}

main

exit 0
